/**********************************************/
/**				buffer.c by doom119			  */
/**					June, 2010				  */
/**********************************************/
// for simple, buffer area starts from 1M to 4M

#include <fs.h>
#include <task.h>
#include <blk.h>
#include <system.h>

#define BUFFER_END (4<<22) // 4M
#define BUFFER_START (1<<20) //1M
#define NR_HASH 307

#define hash_fn(dev, block) (((unsigned)(dev)^(block))%NR_HASH)
#define hash(dev, block) hash_table[hash_fn(dev, block)]
#define BADNESS(bh) (((bh)->dirt<<1)+(bh)->lock)

static struct buffer_head* free_list = 0;
static struct task* buffer_wait = 0;
struct buffer_head* hash_table[NR_HASH];
int NR_BUFFERS = 0;

static inline void wait_on_buffer(struct buffer_head* bh)
{
	cli();
	if(bh->lock)
		sleep_on(&bh->wait);
	sti();
}

struct buffer_head* find_buffer(int dev, int block)
{
	struct buffer_head *tmp;
	
	for(tmp = hash(dev, block); tmp != 0; tmp = tmp->next)
	{
		if(tmp->dev == dev && tmp->blocknr == block)
			return tmp;
	}
	return 0;
}

void sync_dev(int dev)
{
	int i;
	struct buffer_head *bh;

	bh = (struct buffer_head*)BUFFER_START;
	for(i = 0; i<NR_BUFFERS; ++i)
	{
		if(bh->dev != dev)
			continue;
		wait_on_buffer(bh);
		if(bh->dev == dev && bh->dirt)
			ll_rw_block(WRITE, bh);
	}

	// sync_inodes is unfinished****************
}

struct buffer_head* get_hash_table(int dev, int block)
{
	struct buffer_head *bh;

	for(;;)
	{
		if(!(bh = find_buffer(dev, block)))
			return 0;

		bh->count++;;
		wait_on_buffer(bh);
		if(bh->dev == dev && bh->blocknr == block)
			return bh;
		bh->count--;
	}
}

static inline void remove_from_queues(struct buffer_head * bh)
{
	if(bh->next)
		bh->next->prev = bh->prev;
	if(bh->prev)
		bh->prev->next = bh->next;

	if(hash(bh->dev, bh->blocknr) == bh)
		hash(bh->dev, bh->blocknr) = bh->next;

	if(!(bh->prev_free) || !(bh->next_free))
		panic("remove queues 1");
	bh->prev_free->next_free = bh->next_free;
	bh->next_free->prev_free = bh->prev_free;
	if(free_list == bh)
		free_list = bh->next_free;
}

static inline void insert_into_queues(struct buffer_head * bh)
{
	bh->prev_free = free_list->prev_free;
	free_list->prev_free->next_free = bh;
	bh->next_free = free_list;
	free_list->prev_free = bh;

	bh->prev = 0;
	bh->next = 0;
	if(!bh->dev)
		return;

	bh->next = hash(bh->dev, bh->blocknr);
	hash(bh->dev, bh->blocknr) = bh;
	bh->next->prev = bh;
}

struct buffer_head* getblk(int dev, int block)
{
	struct buffer_head *tmp, *bh;

repeat:
	if(bh == get_hash_table(dev, block))
		return bh;

	tmp = free_list;
	do
	{
		if(tmp->count)
			continue;
		if(!bh || BADNESS(tmp)<BADNESS(bh))
		{
			bh = tmp;
			if(!BADNESS(tmp))
				break;
		}
	}while((tmp = tmp->next_free) != free_list);

	if(!bh)
	{
		sleep_on(&buffer_wait);
		goto repeat;
	}

	wait_on_buffer(bh);
	if(bh->count)
		goto repeat;

	while(bh->dirt)
	{
		sync_dev(dev);
		wait_on_buffer(bh);
		if(bh->count)
			goto repeat;
	}

	if(find_buffer(dev, block))
		goto repeat;

	// finnally, we got the buffer
	bh->count = 1;
	bh->dirt = 0;
	bh->uptodate = 0;
	remove_from_queues(bh);
	bh->dev = dev;
	bh->blocknr = block;
	insert_into_queues(bh);
}

struct buffer_head* bread(int dev, int block)
{
	struct buffer_head *bh;

	if(!(bh = getblk(dev, block)))
		panic("bread: getblk  ");
	if(bh->uptodate)
		return bh;
	ll_rw_block(READ, bh);
	wait_on_buffer(bh);
	if(bh->uptodate)
		return bh;

	// this indicates that an occur happened when reading
	brelease(bh);
	return 0;
}

void brelease(struct buffer_head* bh)
{
	if(!bh)
		return;
	wait_on_buffer(bh);
	if(!(bh->count--))
		panic("brelease       ");
	wake_up(&buffer_wait);
}

void buffer_init()
{
	void *b = (void*)BUFFER_END;
	struct buffer_head * h = (struct buffer_head*)BUFFER_START;
	int i = 0;

	while( (b -= BLOCK_SIZE) > (h+1) )
	{
		h->dev = 0;
		h->dirt = 0;
		h->count = 0;
		h->uptodate = 0;
		h->lock = 0;
		h->wait = 0;
		h->next = 0;
		h->prev = 0;
		h->prev_free = h-1;
		h->next_free = h+1;
		h->data = (char*)b;
		NR_BUFFERS++;
		h++;
	}
	h--;
	free_list = (struct buffer_head*)BUFFER_START;
	h->next_free = free_list;
	free_list->prev_free = h;

	for(; i < NR_HASH; ++i)
		hash_table[i] = 0;
	printk("buffer_init OK ");
}


